package com.example.service.impl;

import java.math.BigDecimal;
import java.util.List;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.example.entity.Order;
import com.example.entity.Product;
import com.example.feign.ProductFeignClient;
import com.example.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    // nacos提供的Bean用于获取nacos注册的服务
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private ProductFeignClient productFeignClient;

    /**
     * 业务逻辑可能会因为Sentinel规则被阻塞(BlockException)
     * 也可能会因为自身代码出错而抛出业务异常(Throwable)
     */
    @SentinelResource(
            value = "createOrder",
            blockHandler = "createOrderBlockHandler",
            fallback = "createOrderFallback")
    @Override
    public Order createOrder(Long productId, Long userId) {
        // 使用feign进行远程调用
        Product product = productFeignClient.getProductById(productId);
        Order order = new Order();
        order.setId(productId);
        // TODO 总金额，
        order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
        order.setUserId(userId);
        order.setProductName(product.getProductName());
        order.setAddress("myHome");
        // TODO 远程查询
        order.setProductList(null);

        return order;
    }

    // 兜底回调
    // blockHandler 方法必须位于同一个类中，并且有相同的参数列表和返回类型，此外可以额外在最后接收一个 BlockException 参数。
    public Order createOrderBlockHandler(Long productId, Long userId, BlockException e) {
        Order order = new Order();
        order.setId(productId);
        order.setTotalAmount(new BigDecimal("0"));
        order.setUserId(userId);
        order.setProductName("违反sentinel规则");
        order.setAddress("未知异常："+e.getClass());

        return order;
    }

    // 业务逻辑异常
    public Order createOrderFallback(Long productId, Long userId, Throwable th) {
        Order order = new Order();
        order.setId(productId);
        order.setTotalAmount(new BigDecimal("0"));
        order.setUserId(userId);
        order.setProductName("未知异常");
        order.setAddress("未知异常："+ th.getMessage());

        return order;
    }
    /**
     * 1、使用nacos的DiscoveryClient 远程调用基本实现
     *
     * @param productId
     * @return
     */
    public Product getProductFromRoute(Long productId) {
        // 1、获取服务名为product的所有实例（端口）
        List<ServiceInstance> instance = discoveryClient.getInstances("service-product");
        // 拿到第一个服务发送请求
        ServiceInstance serviceInstance = instance.get(0);
        String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/" + productId;
        String url1 = serviceInstance.getUri().toString();
        log.info("远程请求" + url);
        log.info("远程请求" + url1);

        //2、给远程发送请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

    /**
     * 2、实现负载均衡发送请求
     */
    public Product getProductFromRouteBalance(Long productId) {
        // 1、负载均衡的获取服务名为product的一个实例（端口）
        ServiceInstance instance = loadBalancerClient.choose("service-product");
        String url = "http://" + instance.getHost() + ":" + instance.getPort() + "/product/" + productId;
        log.info("远程请求" + url);
        //2、给远程发送请求
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }

    /**
     * 3、基于注解的负载均衡，对restTemplate添加注解@LoadBalanced
     */
    public Product getProductFromRouteBalanceAnnotation(Long productId) {
        String url = "http://product/" + productId;
        //2、给远程发送请求 product服务名会被动态替换
        Product product = restTemplate.getForObject(url, Product.class);
        return product;
    }
}
